<?php
/**
 * Note:
 * 支付时, 随机选一个可用payChannel, 使用其配置;
 * 通知时, 先查询订单信息, 根据订单中的 payChannel, 进行后续校验(查订单等);
 * 要求各个商户配置的接口密钥相同, 方便前期的校验通过.
 */
namespace App\Services\Pay;

use App\Models\Game\Center\PayOrderProfitSharing;

class PayWechatProfitSharing extends PayWechat
{
    public const API_PROFIT_SHARING_ADD_RECEIVER = 'https://api.mch.weixin.qq.com/pay/profitsharingaddreceiver';
    public const API_PROFIT_SHARING_QUERY = 'https://api.mch.weixin.qq.com/pay/profitsharingquery';
    public const API_PROFIT_SHARING = 'https://api.mch.weixin.qq.com/secapi/pay/profitsharing';

    /**
     * 随机字符串
     * @param int $length
     * @return string
     */
    public function createNoncestr(int $length = 32): string
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    /**
     * 格式化参数格式化成url参数  生成签名sign
     * @param $Obj
     * @param $key
     * @param bool $bool
     * @return string
     */
    public function customSign($Obj, $key, bool $bool = true): string
    {
        foreach ($Obj as $k => $v) {
            $Parameters[$k] = $v;
        }

        //签名步骤一：按字典序排序参数
        ksort($Parameters); // ksort — 对数组按照键名排序
        $String = $this->formatBizQueryParaMap($Parameters, false);

        //签名步骤二：在string后加入KEY
        if ($key) {
            $String = $String . "&key=" . $key;
        }

        if ($bool) {
            //签名步骤三：MD5加密
            $String = md5($String);
        } else {
            //HMAC-SHA256签名方式
            $String = hash_hmac("sha256", $String, $key);
        }

        //签名步骤四：所有字符转为大写
        $result_ = strtoupper($String);
        return $result_;
    }

    /**
     * 按字典序排序参数
     * @param $paraMap
     * @param $urlencode
     * @return false|string
     */
    public function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if ($urlencode) {
                $v = urlencode($v); //urlencode — 编码 URL 字符串
            }
            //$buff .= strtolower($k) . "=" . $v . "&";
            $buff .= $k . "=" . $v . "&";
        }
        $reqPar = '';
        if (strlen($buff) > 0) {
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }

    /**
     * 将数组转换为xml格式
     * @param $arr
     * @param $bool
     * @return string
     */
    public function arrayToXml($arr, $bool): string
    {
        $xml = "<xml>";
        foreach ($arr as $key => $val) {
            if ($bool) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else{
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    /**
     * 发送请求
     * @param $xml
     * @param $url
     * @param array $options
     * @return false|mixed
     */
    public function sendPrePayCurl($xml, $url = '', array $options = array())
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        if (!empty($options)) {
            curl_setopt_array($ch, $options);
        }
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        $data = curl_exec($ch);
        curl_close($ch);

        $data_xml_arr = $this->XMLDataParse($data);
        if ($data_xml_arr) {
            return $data_xml_arr;
        } else {
            return false;
        }
    }

    /**
     * xml格式数据解析函数
     * @param $data
     * @return mixed
     */
    public function XMLDataParse($data)
    {
        $xml = simplexml_load_string($data, NULL, LIBXML_NOCDATA);
        $array = json_decode(json_encode($xml), true);
        return $array;
    }

    /**
     * 添加分账接收方
     * @return void
     * @throws \Exception
     */
    public function addProfitSharingReceiver()
    {
        $params = array();
        $params['mch_id'] = $this->config['mch_id'];// 商户号
        $params['sub_mch_id'] = $this->config['sub_mch_id'];// 子商户号
        $params['appid'] = $this->config['app_id'];// 公众账号ID
        $params['nonce_str'] = $this->createNoncestr();// 随机字符串
        $params['sign_type'] = "HMAC-SHA256";// 签名类型
        $params["receiver"] = json_encode($this->config['profit_sharing_receiver']);
        // 签名
        $params["sign"] = $this->customSign($params, $this->config['key'], false);
        //把数组转化成xml格式
        $xmlData = $this->arrayToXml($params, true);

        //发送请求
        $res = $this->sendPrePayCurl($xmlData, self::API_PROFIT_SHARING_ADD_RECEIVER);
        info('添加分账接收方返回结果:', $res);
        if ($res['result_code'] !== 'SUCCESS') throw new \Exception($res['return_msg'] ?? '添加分账接收方失败');
        return ;
    }

    /**
     * 发送分账
     * @param PayOrderProfitSharing $profitSharing
     * @return array
     * @throws \Exception
     */
    public function sendProfitSharingReceiver(PayOrderProfitSharing $profitSharing): array
    {
        $params = array();
        $params["appid"] = $this->config['app_id'];
        $params["mch_id"] = $this->config['mch_id'];
        $params["sub_mch_id"] = $this->config['sub_mch_id'];
        $params["nonce_str"] = $this->createNoncestr();
        $params["out_order_no"] = $profitSharing->orderid;
        $params["transaction_id"] = $profitSharing->orderid_pay;
        $params["sign_type"] = "HMAC-SHA256";

        // 分账接收方列表
        $receivers = [
            [
                'type' => 'MERCHANT_ID',
                'account' => $this->config['mch_id'],
                'amount' => $profitSharing->money_share,
                'description' => '分到服务商',
            ],
        ];

        $params["receivers"] = json_encode($receivers);
        $params["sign"] = $this->customSign($params, $this->config['key'], false);
        $xmlData = $this->arrayToXml($params, true);

        $options = array(
            CURLOPT_SSLCERT => $this->config['cert_client'],// 客户端证书，用于双向认证
            // CURLOPT_SSLCERTTYPE => 'PEM',// 证书的类型。支持的格式有"PEM" (默认值), "DER"和"ENG"。
            CURLOPT_SSLKEY => $this->config['cert_key'],// 客户端私钥的文件路径
            // CURLOPT_SSLKEYTYPE => 'PEM',// 客户端私钥类型，支持的私钥类型为"PEM"(默认值)、"DER"和"ENG"。
            // CURLOPT_KEYPASSWD => $this->config['mch_id'],//客户端私钥密码，私钥在创建时可以选择加密。
            //API证书调用或安装需要使用到密码，该密码的值为微信商户号（mch_id）
        );

        $res = $this->sendPrePayCurl($xmlData, self::API_PROFIT_SHARING, $options);
        info('分账返回结果:');
        info($res);

        //更新订单数据
        if ($res['result_code'] !== 'SUCCESS') throw new \Exception($res['err_code_des'] ?? '分账失败');

        $status = PayOrderProfitSharing::STATUS_WAIT;
        switch ($res['status']) {
            case 'PROCESSING':
                $status = PayOrderProfitSharing::STATUS_PROCESSING;
                break;
            case 'FINISHED':
                $status = PayOrderProfitSharing::STATUS_SUCCESS;
                break;
        }
        return [$status, $res['order_id']];
    }

    /**
     * 计算待分账金额, 四舍五入, 最低1分
     * @param int $money
     * @return int
     */
    public function calcProfitSharingMoney(int $money): int
    {
        // 金额太少时, round的结果可能为0, 使用max, 最低1
        $share = $money * $this->config['profit_sharing_scale'];
        return max(round($share), 1);
    }

    /**
     * 查询分账结果
     * @param PayOrderProfitSharing $profitSharing
     * @return array
     * @throws \Exception
     */
    public function queryProfitSharing(PayOrderProfitSharing $profitSharing)
    {
        $params = array();
        $params['mch_id'] = $this->config['mch_id'];// 商户号
        $params['sub_mch_id'] = $this->config['sub_mch_id'];
        $params['transaction_id'] = $profitSharing->orderid_pay;
        $params['out_order_no'] = $profitSharing->orderid;
        $params['nonce_str'] = $this->createNoncestr();
        $params['sign_type'] = "HMAC-SHA256";
        // 签名
        $params["sign"] = $this->customSign($params, $this->config['key'], false);
        //把数组转化成xml格式
        $xmlData = $this->arrayToXml($params, true);

        //发送请求
        $res = $this->sendPrePayCurl($xmlData, self::API_PROFIT_SHARING_QUERY);
        info('分账查询结果:');
        info($res);

        //更新订单数据
        if ($res['result_code'] !== 'SUCCESS') throw new \Exception($res['return_msg'] ?? '查询分账结果失败');

        $status = $profitSharing->status;
        switch ($res['status']) {
            case 'PROCESSING':
                $status = PayOrderProfitSharing::STATUS_PROCESSING;
                break;
            case 'FINISHED':
                $status = PayOrderProfitSharing::STATUS_SUCCESS;
                break;
        }
        return [$status, $res['order_id']];
    }
}
